contents

자바에서 Iterator 는 컬렉션의 내부 구조(예: ArrayList, LinkedList, HashSet인지 여부)를 노출하지 않고, 컬렉션의 원소를 하나씩 순회하는 표준적이고 보편적인 방법을 제공하는 인터페이스입니다.


Iterator가 해결하는 문제

리스트의 경우 인덱스를 사용하는 일반 for 루프를 사용할 수 있지만, 인덱스가 없는 세트(Set)나 다른 유형의 컬렉션에서는 작동하지 않습니다. Iterator는 두 가지 주요 문제를 해결합니다.

  1. 보편적인 순회: Iterable 인터페이스를 구현하는 모든 컬렉션을 순회할 수 있는 단일하고 일관된 방법(.iterator())을 제공합니다. 이는 내부 세부 사항을 추상화하므로, ArrayList를 순회하는 코드와 HashSet을 순회하는 코드가 완전히 동일할 수 있습니다.
  2. 반복 중 안전한 제거: 이것이 Iterator를 직접 사용하는 가장 중요한 이유입니다. 일반적인 for-each 루프를 사용하여 컬렉션을 순회하는 동안 항목을 제거하려고 하면 ConcurrentModificationException이 발생합니다. Iterator는 컬렉션을 순회하면서 수정할 수 있는 유일하고 안전한 방법을 제공합니다.

Iterator 인터페이스: 핵심 메서드 🚶‍♂️

Iterator 인터페이스는 매우 단순하며 세 가지 핵심 메서드를 가지고 있습니다.

자바 8에서는 남은 모든 원소에 대해 주어진 작업을 수행하는 forEachRemaining(action)이라는 디폴트 메서드가 추가되었습니다.


Iterator 사용 방법

모든 컬렉션 객체에서 .iterator() 메서드를 호출하여 Iterator 인스턴스를 얻을 수 있습니다. 표준적인 사용법은 while 루프와 함께 사용하는 것입니다.

기본 순회

List fruits = new ArrayList<>(List.of("Apple", "Banana", "Cherry"));

Iterator iterator = fruits.iterator();

while (iterator.hasNext()) {
    String fruit = iterator.next();
    System.out.println(fruit);
}

올바른 방법: 안전한 제거 🗑️

반복 중에 원소를 안전하게 제거하는 방법은 다음과 같습니다. 핵심은 iterator.remove() 메서드를 사용하는 것입니다.

List fruits = new ArrayList<>(List.of("Apple", "Banana", "Cherry"));
Iterator iterator = fruits.iterator();

while (iterator.hasNext()) {
    String fruit = iterator.next();
    if (fruit.equals("Banana")) {
        // 이것이 반복 중에 제거하는 유일하고 안전한 방법입니다.
        iterator.remove(); 
    }
}
System.out.println(fruits); // 출력: [Apple, Cherry]

중요: remove()next() 호출 한 번당 한 번만 호출할 수 있습니다. next()를 적어도 한 번 호출하기 전에는 remove()를 호출할 수 없습니다.

잘못된 방법: 안전하지 않은 제거

for-each 루프 내에서 컬렉션 자체의 remove() 메서드를 사용하려고 하면 코드가 충돌합니다.

List fruits = new ArrayList<>(List.of("Apple", "Banana", "Cherry"));

try {
    for (String fruit : fruits) {
        if (fruit.equals("Banana")) {
            // 잘못된 방법! ConcurrentModificationException이 발생합니다.
            fruits.remove(fruit);
        }
    }
} catch (ConcurrentModificationException e) {
    System.out.println("오류: " + e.getMessage());
}

For-Each 루프: 문법적 설탕 (Syntactic Sugar) 🍬

"for-each 루프가 제거에 그렇게 위험하다면 왜 존재하는 걸까?"라고 궁금해할 수 있습니다. 그 답은 for-each 루프(향상된 for문이라고도 함)가 사실은 Iterator를 위한 문법적 설탕에 불과하기 때문입니다.

다음과 같이 코드를 작성하면:

for (String fruit : fruits) {
    System.out.println(fruit);
}

자바 컴파일러는 실제로 내부적으로 이것을 다음과 같이 변환합니다:

Iterator iterator = fruits.iterator();
while (iterator.hasNext()) {
    String fruit = iterator.next();
    System.out.println(fruit);
}

이것이 ConcurrentModificationException이 발생하는 이유입니다. for-each 루프는 순회를 관리하기 위해 비밀리에 이터레이터를 사용하고 있습니다. 이때 fruits.remove()를 호출하면, 이터레이터 모르게 리스트를 직접 수정하는 셈이 됩니다. 이터레이터는 다음 작업에서 이 외부 수정을 감지하고 예측할 수 없는 동작을 방지하기 위해 예외를 던집니다.


관련 인터페이스

references